//----------------------------------------------------------------------------
//
// Callback notification routines.
//
// Copyright (C) Microsoft Corporation, 2000-2002.
//
//----------------------------------------------------------------------------

#include "ntsdp.hpp"

// Event APCs end up calling out to external code so it's difficult
// to pick a reasonable timeout.  However, we need to ensure that
// a server won't hang indefinitely if something goes wrong with
// event notifications.
#define EVENT_APC_TIMEOUT 300000

PSTR
EventIdStr(void)
{
    static char s_Buf[128];

    if (IS_USER_TARGET(g_EventTarget))
    {
        PrintString(s_Buf, DIMA(s_Buf), "(%x.%x): ",
                    g_EventProcessSysId, g_EventThreadSysId);
    }
    else
    {
        s_Buf[0] = 0;
    }
    return s_Buf;
}

//----------------------------------------------------------------------------
//
// APC support for dispatching event callbacks on the proper thread.
//
//----------------------------------------------------------------------------

struct AnyApcData
{
    AnyApcData(ULONG Mask, PCSTR Name)
    {
        m_Mask = Mask;
        m_Name = Name;
    }
    
    ULONG m_Mask;
    PCSTR m_Name;

    virtual ULONG Dispatch(DebugClient* Client) = 0;
};

struct BreakpointEventApcData : public AnyApcData
{
    BreakpointEventApcData()
        : AnyApcData(DEBUG_EVENT_BREAKPOINT,
                     "IDebugEventCallbacks::Breakpoint")
    {
    }
    
    Breakpoint* m_Bp;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        if ((m_Bp->m_Flags & DEBUG_BREAKPOINT_ADDER_ONLY) == 0 ||
            Client == m_Bp->m_Adder)
        {
            return Client->m_EventCb->
                Breakpoint(m_Bp);
        }
        else
        {
            return DEBUG_STATUS_NO_CHANGE;
        }
    }
};

struct ExceptionEventApcData : public AnyApcData
{
    ExceptionEventApcData()
        : AnyApcData(DEBUG_EVENT_EXCEPTION,
                     "IDebugEventCallbacks::Exception")
    {
    }
    
    PEXCEPTION_RECORD64 m_Record;
    ULONG m_FirstChance;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            Exception(m_Record, m_FirstChance);
    }
};

struct CreateThreadEventApcData : public AnyApcData
{
    CreateThreadEventApcData()
        : AnyApcData(DEBUG_EVENT_CREATE_THREAD,
                     "IDebugEventCallbacks::CreateThread")
    {
    }
    
    ULONG64 m_Handle;
    ULONG64 m_DataOffset;
    ULONG64 m_StartOffset;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            CreateThread(m_Handle, m_DataOffset, m_StartOffset);
    }
};

struct ExitThreadEventApcData : public AnyApcData
{
    ExitThreadEventApcData()
        : AnyApcData(DEBUG_EVENT_EXIT_THREAD,
                     "IDebugEventCallbacks::ExitThread")
    {
    }
    
    ULONG m_ExitCode;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            ExitThread(m_ExitCode);
    }
};

struct CreateProcessEventApcData : public AnyApcData
{
    CreateProcessEventApcData()
        : AnyApcData(DEBUG_EVENT_CREATE_PROCESS,
                     "IDebugEventCallbacks::CreateProcess")
    {
    }
    
    ULONG64 m_ImageFileHandle;
    ULONG64 m_Handle;
    ULONG64 m_BaseOffset;
    ULONG m_ModuleSize;
    PCSTR m_ModuleName;
    PCSTR m_ImageName;
    ULONG m_CheckSum;
    ULONG m_TimeDateStamp;
    ULONG64 m_InitialThreadHandle;
    ULONG64 m_ThreadDataOffset;
    ULONG64 m_StartOffset;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            CreateProcess(m_ImageFileHandle, m_Handle, m_BaseOffset,
                          m_ModuleSize, m_ModuleName, m_ImageName,
                          m_CheckSum, m_TimeDateStamp, m_InitialThreadHandle,
                          m_ThreadDataOffset, m_StartOffset);
    }
};

struct ExitProcessEventApcData : public AnyApcData
{
    ExitProcessEventApcData()
        : AnyApcData(DEBUG_EVENT_EXIT_PROCESS,
                     "IDebugEventCallbacks::ExitProcess")
    {
    }
    
    ULONG m_ExitCode;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            ExitProcess(m_ExitCode);
    }
};

struct LoadModuleEventApcData : public AnyApcData
{
    LoadModuleEventApcData()
        : AnyApcData(DEBUG_EVENT_LOAD_MODULE,
                     "IDebugEventCallbacks::LoadModule")
    {
    }
    
    ULONG64 m_ImageFileHandle;
    ULONG64 m_BaseOffset;
    ULONG m_ModuleSize;
    PCSTR m_ModuleName;
    PCSTR m_ImageName;
    ULONG m_CheckSum;
    ULONG m_TimeDateStamp;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            LoadModule(m_ImageFileHandle, m_BaseOffset, m_ModuleSize,
                       m_ModuleName, m_ImageName, m_CheckSum,
                       m_TimeDateStamp);
    }
};

struct UnloadModuleEventApcData : public AnyApcData
{
    UnloadModuleEventApcData()
        : AnyApcData(DEBUG_EVENT_UNLOAD_MODULE,
                     "IDebugEventCallbacks::UnloadModule")
    {
    }
    
    PCSTR m_ImageBaseName;
    ULONG64 m_BaseOffset;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            UnloadModule(m_ImageBaseName, m_BaseOffset);
    }
};

struct SystemErrorEventApcData : public AnyApcData
{
    SystemErrorEventApcData()
        : AnyApcData(DEBUG_EVENT_SYSTEM_ERROR,
                     "IDebugEventCallbacks::SystemError")
    {
    }
    
    ULONG m_Error;
    ULONG m_Level;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            SystemError(m_Error, m_Level);
    }
};

struct SessionStatusApcData : public AnyApcData
{
    SessionStatusApcData()
        : AnyApcData(DEBUG_EVENT_SESSION_STATUS,
                     "IDebugEventCallbacks::SessionStatus")
    {
    }
    
    ULONG m_Status;
    
    virtual ULONG Dispatch(DebugClient* Client)
    {
        return Client->m_EventCb->
            SessionStatus(m_Status);
    }
};

ULONG
ApcDispatch(DebugClient* Client, AnyApcData* ApcData, ULONG EventStatus)
{
    DBG_ASSERT(Client->m_EventCb != NULL);

    HRESULT Vote;

    __try
    {
        Vote = ApcData->Dispatch(Client);
    }
    __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                      NULL, ApcData->m_Name))
    {
        Vote = DEBUG_STATUS_NO_CHANGE;
    }
            
    return MergeVotes(EventStatus, Vote);
}

void APIENTRY
EventApc(ULONG_PTR Param)
{
    AnyApcData* ApcData = (AnyApcData*)Param;
    ULONG Tid = GetCurrentThreadId();
    DebugClient* Client;
    ULONG EventStatus = DEBUG_STATUS_NO_CHANGE;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_ThreadId == Tid &&
            (Client->m_EventInterest & ApcData->m_Mask))
        {
            EventStatus = ApcDispatch(Client, ApcData, EventStatus);
        }
    }

    if (WaitForSingleObject(g_EventStatusWaiting, EVENT_APC_TIMEOUT) !=
        WAIT_OBJECT_0)
    {
        ErrOut("Unable to wait for StatusWaiting, %d\n",
               GetLastError());
        EventStatus = WIN32_LAST_STATUS();
    }

    g_EventStatus = EventStatus;
    
    if (!SetEvent(g_EventStatusReady))
    {
        ErrOut("Unable to set StatusReady, %d\n",
               GetLastError());
        g_EventStatus = WIN32_LAST_STATUS();
    }
}

ULONG
SendEvent(AnyApcData* ApcData, ULONG EventStatus)
{
    DebugClient* Client;
    ULONG NumQueued = 0;
    ULONG TidDone = 0;
    static ULONG s_TidSending = 0;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        // Only queue one APC per thread regardless of how
        // many clients.  The APC function will deliver the
        // callback to all clients on that thread.
        if (Client->m_ThreadId != TidDone &&
            (Client->m_EventInterest & ApcData->m_Mask))
        {
            // SessionStatus callbacks are made at unusual
            // times so do not do full call preparation.
            if (TidDone == 0 &&
                ApcData->m_Mask != DEBUG_EVENT_SESSION_STATUS)
            {
                PrepareForCalls(DEBUG_STATUS_INSIDE_WAIT);
            }

            if (Client->m_ThreadId == GetCurrentThreadId())
            {
                // Don't hold the engine lock while the client
                // is called.
                SUSPEND_ENGINE();
                
                EventStatus = ApcDispatch(Client, ApcData, EventStatus);

                RESUME_ENGINE();
            }
            else if (QueueUserAPC(EventApc, Client->m_Thread,
                                  (ULONG_PTR)ApcData))
            {
                TidDone = Client->m_ThreadId;
                NumQueued++;
            }
            else
            {
                ErrOut("Unable to deliver callback, %d\n", GetLastError());
            }
        }
    }

    if (NumQueued == 0)
    {
        // No APCs queued.
        return EventStatus;
    }

    // This function's use of global data is only safe as
    // long as a single send is active at once.  Synchronous
    // sends are almost exclusively sent by the session thread
    // so competition to send is very rare, therefore we
    // don't really attempt to handle it.
    if (s_TidSending != 0)
    {
        return E_FAIL;
    }
    s_TidSending = GetCurrentThreadId();

    // Leave the lock while waiting.
    SUSPEND_ENGINE();
    
    while (NumQueued-- > 0)
    {
        if (!SetEvent(g_EventStatusWaiting))
        {
            // If the event can't be set everything is hosed
            // and threads may be stuck waiting so we
            // just panic.
            ErrOut("Unable to set StatusWaiting, %d\n",
                   GetLastError());
            EventStatus = WIN32_LAST_STATUS();
            break;
        }

        for (;;)
        {
            ULONG Wait;
            
            Wait = WaitForSingleObjectEx(g_EventStatusReady,
                                         EVENT_APC_TIMEOUT, TRUE);
            if (Wait == WAIT_OBJECT_0)
            {
                EventStatus = MergeVotes(EventStatus, g_EventStatus);
                break;
            }
            else if (Wait != WAIT_IO_COMPLETION)
            {
                ErrOut("Unable to wait for StatusReady, %d\n",
                       GetLastError());
                EventStatus = WIN32_LAST_STATUS();
                NumQueued = 0;
                break;
            }
        }
    }

    RESUME_ENGINE();
    s_TidSending = 0;
    return EventStatus;
}

//----------------------------------------------------------------------------
//
// Event callbacks.
//
//----------------------------------------------------------------------------

ULONG g_EngNotify;

ULONG
ExecuteEventCommand(ULONG EventStatus, DebugClient* Client, PCSTR Command)
{
    if (Command == NULL)
    {
        return EventStatus;
    }
    
    // Don't output any noise while processing event
    // command strings.
    BOOL OldOutReg = g_OciOutputRegs;
    g_OciOutputRegs = FALSE;

    PrepareForCalls(DEBUG_STATUS_INSIDE_WAIT);
    // Stop execution as soon as the execution status
    // changes.
    g_EngStatus |= ENG_STATUS_NO_AUTO_WAIT;
        
    Execute(Client, Command, DEBUG_EXECUTE_NOT_LOGGED);
        
    g_EngStatus &= ~ENG_STATUS_NO_AUTO_WAIT;
    g_OciOutputRegs = OldOutReg;

    // Translate the continuation status from
    // the state the engine was left in by the command.
    if (IS_RUNNING(g_CmdState))
    {
        // If the command left the engine running override
        // the incoming event status.  This allows a user
        // to create conditional commands that can resume
        // execution even when the basic setting may be to break.
        return g_ExecutionStatusRequest;
    }
    else
    {
        return EventStatus;
    }
}

HRESULT
NotifyBreakpointEvent(ULONG Vote, Breakpoint* Bp)
{
    ULONG EventStatus;

    // Processing of commands can delete breakpoints.
    // For example, 'g' will delete any go breakpoints.
    // Preserve this breakpoint as long as we need it.
    Bp->Preserve();
    
    g_LastEventType = DEBUG_EVENT_BREAKPOINT;
    g_LastEventInfo.Breakpoint.Id = Bp->m_Id;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.Breakpoint);
    sprintf(g_LastEventDesc, "Hit breakpoint %d", Bp->m_Id);
    
    // Execute breakpoint command first if one exists.
    if (Bp->m_Command != NULL)
    {
        EventStatus = ExecuteEventCommand(DEBUG_STATUS_NO_CHANGE,
                                          Bp->m_Adder, Bp->m_Command);
    }
    else
    {
        if ((Bp->m_Flags & (BREAKPOINT_HIDDEN |
                            DEBUG_BREAKPOINT_ADDER_ONLY)) == 0)
        {
            StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
            dprintf("Breakpoint %u hit\n", Bp->m_Id);
        }
        
        EventStatus = DEBUG_STATUS_NO_CHANGE;
    }

    BreakpointEventApcData ApcData;
    ApcData.m_Bp = Bp;
    EventStatus = SendEvent(&ApcData, EventStatus);

    Bp->Relinquish();
    
    // If there weren't any votes default to breaking in.
    if (EventStatus == DEBUG_STATUS_NO_CHANGE)
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }

    // Fold command status into votes from previous breakpoints.
    return MergeVotes(Vote, EventStatus);
}

void
ProcessVcppException(PEXCEPTION_RECORD64 Record)
{
    EXCEPTION_VISUALCPP_DEBUG_INFO64 Info64;
    EXCEPTION_VISUALCPP_DEBUG_INFO64* Info;

    SuspendExecution();
    if (!g_EventMachine ||
        !g_EventProcess)
    {
        return;
    }
    
    // If this is a 32-bit system we need to convert
    // back to a 32-bit exception record so that
    // we can properly reconstruct the info from
    // the arguments.
    if (!g_EventMachine->m_Ptr64)
    {
        EXCEPTION_RECORD32 Record32;
        EXCEPTION_VISUALCPP_DEBUG_INFO32* Info32;
        
        ExceptionRecord64To32(Record, &Record32);
        Info32 = (EXCEPTION_VISUALCPP_DEBUG_INFO32*)
            Record32.ExceptionInformation;
        Info = &Info64;
        Info->dwType = Info32->dwType;
        switch(Info->dwType)
        {
        case VCPP_DEBUG_SET_NAME:
            Info->SetName.szName = EXTEND64(Info32->SetName.szName);
            Info->SetName.dwThreadID = Info32->SetName.dwThreadID;
            Info->SetName.dwFlags = Info32->SetName.dwFlags;
            break;
        }
    }
    else
    {
        Info = (EXCEPTION_VISUALCPP_DEBUG_INFO64*)
            Record->ExceptionInformation;
    }

    ThreadInfo* Thread;
    
    switch(Info->dwType)
    {
    case VCPP_DEBUG_SET_NAME:
        if (Info->SetName.dwThreadID == -1)
        {
            Thread = g_EventThread;
        }
        else
        {
            Thread = g_EventProcess->
                FindThreadBySystemId(Info->SetName.dwThreadID);
        }
        if (Thread != NULL)
        {
            DWORD Read;
            
            if (g_EventTarget->ReadVirtual(g_EventProcess,
                                           Info->SetName.szName,
                                           Thread->m_Name,
                                           MAX_THREAD_NAME - 1,
                                           &Read) != S_OK)
            {
                Thread->m_Name[0] = 0;
            }
            else
            {
                Thread->m_Name[Read] = 0;
            }
        }
        break;
    }
}

ULONG
ProcessCommandException(ULONG EventStatus, PEXCEPTION_RECORD64 Record)
{
    if (Record->NumberParameters != 4 ||
        (ULONG)Record->ExceptionInformation[0] != DEBUG_COMMAND_EXCEPTION_ID ||
        Record->ExceptionInformation[1] < DEBUG_CMDEX_ADD_EVENT_STRING ||
        Record->ExceptionInformation[1] > DEBUG_CMDEX_RESET_EVENT_STRINGS)
    {
        // Malformed exception.
        return EventStatus;
    }

    switch(Record->ExceptionInformation[1])
    {
    case DEBUG_CMDEX_ADD_EVENT_STRING:
        if (Record->ExceptionInformation[2] > 0 &&
            Record->ExceptionInformation[2] < 0x100000)
        {
            EventString* Str = g_EventThread->
                AllocEventString((ULONG)Record->ExceptionInformation[2]);
            if (Str == NULL)
            {
                ErrOut("Unable to allocate event string\n");
                return DEBUG_STATUS_BREAK;
            }

            if (g_Target->
                ReadAllVirtual(g_EventProcess,
                               Record->ExceptionInformation[3],
                               Str->String,
                               (ULONG)Record->ExceptionInformation[2]) != S_OK)
            {
                ErrOut("Unable to read event string\n");
                return DEBUG_STATUS_BREAK;
            }

            Str->String[(ULONG)Record->ExceptionInformation[2]] = 0;
            g_EventThread->AppendEventString(Str);
        }
        break;
        
    case DEBUG_CMDEX_RESET_EVENT_STRINGS:
        if (Record->ExceptionInformation[2] == 0 &&
            Record->ExceptionInformation[3] == 0)
        {
            g_EventThread->ClearEventStrings();
        }
        break;
    }
    
    return EventStatus;
}

void
ProcessAssertException(PEXCEPTION_RECORD64 Record)
{
    char FileName[MAX_PATH];
    ULONG Line;
    char Text[2 * MAX_PATH];
    ImageInfo* Image;

    Image = g_EventProcess->FindImageByOffset(Record->ExceptionAddress, FALSE);
    if (!Image ||
        FAILED(Image->FindSysAssert(Record->ExceptionAddress,
                                    FileName, DIMA(FileName),
                                    &Line,
                                    Text, DIMA(Text))))
    {
        return;
    }

    dprintf("Assertion %s(%d): %s\n", FileName, Line, Text);
}

HRESULT
NotifyExceptionEvent(PEXCEPTION_RECORD64 Record,
                     ULONG FirstChance, BOOL OutputDone)
{
    ULONG EventStatus;
    EVENT_FILTER* Filter;
    EVENT_COMMAND* Command;
    PDEBUG_EXCEPTION_FILTER_PARAMETERS Params;
    BOOL FirstChanceMessage = FALSE;

    if (g_EventThread == NULL)
    {
        ErrOut("ERROR: Exception %X occurred on unknown thread %X\n",
               Record->ExceptionCode, g_EventThreadSysId);
    }
    
    g_LastEventType = DEBUG_EVENT_EXCEPTION;
    g_LastEventInfo.Exception.ExceptionRecord = *Record;
    g_LastEventInfo.Exception.FirstChance = FirstChance;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.Exception);

    if (Record->ExceptionCode == STATUS_VCPP_EXCEPTION)
    {
        // Handle special VC++ exceptions as they
        // pass information from the debuggee to the debugger.
        ProcessVcppException(Record);
    }

    Filter = GetSpecificExceptionFilter(Record->ExceptionCode);
    if (Filter == NULL)
    {
        // Use the default filter for name and handling.
        Filter = &g_EventFilters[FILTER_DEFAULT_EXCEPTION];
        GetOtherExceptionParameters(Record->ExceptionCode, TRUE,
                                    &Params, &Command);
    }
    else
    {
        Params = &Filter->Params;
        Command = &Filter->Command;
    }

    g_EngDefer |= ENG_DEFER_EXCEPTION_HANDLING;
    g_EventExceptionFilter = Params;
    g_ExceptionFirstChance = FirstChance;
    
    if (Params->ExecutionOption != DEBUG_FILTER_IGNORE)
    {
        if (!OutputDone)
        {
            StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
            dprintf("%s%s", EventIdStr(), Filter->Name);
            if (Filter->OutArgFormat != NULL)
            {
                dprintf(Filter->OutArgFormat,
                        Record->ExceptionInformation[Filter->OutArgIndex]);
            }

            dprintf(" - code %08lx (%s)\n",
                    Record->ExceptionCode,
                    FirstChance ? "first chance" : "!!! second chance !!!");

            FirstChanceMessage = FirstChance != 0;
        }

        if (Params->ExecutionOption == DEBUG_FILTER_BREAK ||
            (Params->ExecutionOption == DEBUG_FILTER_SECOND_CHANCE_BREAK &&
             !FirstChance))
        {
            EventStatus = DEBUG_STATUS_BREAK;
        }
        else
        {
            EventStatus = DEBUG_STATUS_IGNORE_EVENT;
        }
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    strcpy(g_LastEventDesc, Filter->Name);
    if (Filter->OutArgFormat != NULL)
    {
        sprintf(g_LastEventDesc + strlen(g_LastEventDesc),
                Filter->OutArgFormat,
                Record->ExceptionInformation[Filter->OutArgIndex]);
    }
    sprintf(g_LastEventDesc + strlen(g_LastEventDesc),
            " - code %08lx (%s)",
            Record->ExceptionCode,
            FirstChance ? "first chance" : "!!! second chance !!!");

    if (g_EventThread && Record->ExceptionCode == DBG_COMMAND_EXCEPTION)
    {
        EventStatus = ProcessCommandException(EventStatus, Record);
    }
    
    // If this is the initial breakpoint execute the
    // initial breakpoint command.
    if ((g_EngStatus & ENG_STATUS_AT_INITIAL_BREAK) &&
        IS_EFEXECUTION_BREAK(g_EventFilters[DEBUG_FILTER_INITIAL_BREAKPOINT].
                             Params.ExecutionOption))
    {
        EventStatus = ExecuteEventCommand
            (EventStatus,
             g_EventFilters[DEBUG_FILTER_INITIAL_BREAKPOINT].Command.Client,
             g_EventFilters[DEBUG_FILTER_INITIAL_BREAKPOINT].
             Command.Command[0]);
    }
    
    EventStatus = ExecuteEventCommand(EventStatus,
                                      Command->Client,
                                      Command->Command[FirstChance ? 0 : 1]);
    
    ExceptionEventApcData ApcData;
    ApcData.m_Record = Record;
    ApcData.m_FirstChance = FirstChance;
    EventStatus = SendEvent(&ApcData, EventStatus);

    if (FirstChanceMessage && EventStatus == DEBUG_STATUS_BREAK)
    {
        // Show a verbose message for first-chance exceptions
        // to try and explain to users that they may not
        // necessarily be a problem.  Don't show the message
        // for hard-coded break instructions as those are
        // common and very rarely handled.
        if (Record->ExceptionCode != STATUS_BREAKPOINT &&
            Record->ExceptionCode != STATUS_WAKE_SYSTEM_DEBUGGER)
        {
            dprintf("First chance exceptions are reported "
                    "before any exception handling.\n");
            dprintf("This exception may be expected and handled.\n");
        }
    }

    return EventStatus;
}

HRESULT
NotifyCreateThreadEvent(ULONG64 Handle,
                        ULONG64 DataOffset,
                        ULONG64 StartOffset,
                        ULONG Flags)
{
    ProcessInfo* Process;
    
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Create thread %x:%x\n",
            g_EventProcessSysId, g_EventThreadSysId);

    if ((Process = g_EventTarget->
         FindProcessBySystemId(g_EventProcessSysId)) == NULL)
    {
        ErrOut("Unable to find system process %x\n", g_EventProcessSysId);

        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
            sprintf(g_LastEventDesc, "Create unowned thread %x for %x",
                    g_EventThreadSysId, g_EventProcessSysId);
        }
        
        // Can't really continue the notification.
        return DEBUG_STATUS_BREAK;
    }

    ThreadInfo* Thread;
    
    // There's a small window when attaching during process creation where
    // it's possible to get two create thread events for the
    // same thread.  Check and see if this process already has
    // a thread with the given ID and handle.
    // If a process attach times out and the process is examined,
    // there's a possibility that the attach may succeed later,
    // yielding events for processes and threads already created
    // by examination.  In that case just check for an ID match
    // as the handles will be different.

    ForProcessThreads(Process)
    {
        if (((Process->m_Flags & ENG_PROC_EXAMINED) ||
             Thread->m_Handle == Handle) &&
            Thread->m_SystemId == g_EventThreadSysId)
        {
            // We already know about this thread, just
            // ignore the event.
            if ((Process->m_Flags & ENG_PROC_EXAMINED) == 0)
            {
                WarnOut("WARNING: Duplicate thread create event for %x:%x\n",
                        g_EventProcessSysId, g_EventThreadSysId);
            }
            return DEBUG_STATUS_IGNORE_EVENT;
        }
    }
    
    Thread = new ThreadInfo(Process, g_EventThreadSysId,
                            DataOffset, Handle, Flags, StartOffset);
    if (Thread == NULL)
    {
        ErrOut("Unable to allocate thread record for create thread event\n");
        ErrOut("Thread %x:%x will be lost\n",
               g_EventProcessSysId, g_EventThreadSysId);

        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
            sprintf(g_LastEventDesc, "Can't create thread %x for %x",
                    g_EventThreadSysId, g_EventProcessSysId);
        }
        
        // Can't really continue the notification.
        return DEBUG_STATUS_BREAK;
    }
    
    // Look up infos now that they've been added.
    FindEventProcessThread();
    if (g_EventProcess == NULL || g_EventThread == NULL)
    {
        // This should never happen with the above failure
        // checks but handle it just in case.
        ErrOut("Create thread unable to locate process or thread %x:%x\n",
               g_EventProcessSysId, g_EventThreadSysId);
        return DEBUG_STATUS_BREAK;
    }

    VerbOut("Thread created: %lx.%lx\n",
            g_EventProcessSysId, g_EventThreadSysId);

    if (g_EngNotify > 0)
    {
        // This call is just to update internal thread state.
        // Do not make real callbacks.
        return DEBUG_STATUS_NO_CHANGE;
    }

    g_EventTarget->OutputProcessesAndThreads("*** Create thread ***");

    g_LastEventType = DEBUG_EVENT_CREATE_THREAD;
    sprintf(g_LastEventDesc, "Create thread %d:%x",
            g_EventThread->m_UserId, g_EventThreadSysId);
    
    // Always update breakpoints to account for the new thread.
    SuspendExecution();
    RemoveBreakpoints();
    g_UpdateDataBreakpoints = TRUE;
    g_DataBreakpointsChanged = TRUE;
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_CREATE_THREAD];

    EventStatus =
        IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;
    
    EventStatus = ExecuteEventCommand(EventStatus,
                                      Filter->Command.Client,
                                      Filter->Command.Command[0]);
    
    CreateThreadEventApcData ApcData;
    ApcData.m_Handle = Handle;
    ApcData.m_DataOffset = DataOffset;
    ApcData.m_StartOffset = StartOffset;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyExitThreadEvent(ULONG ExitCode)
{
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Exit thread\n");

    g_EngDefer |= ENG_DEFER_DELETE_EXITED;
    // There's a small possibility that exit events can
    // be delivered when the engine is not expecting them.
    // When attaching to a process that's exiting it's possible
    // to get an exit but no create.  When restarting it's
    // possible that not all events were successfully drained.
    // Protect this code from faulting in that case.
    if (g_EventThread == NULL)
    {
        WarnOut("WARNING: Unknown thread exit: %lx.%lx\n",
                g_EventProcessSysId, g_EventThreadSysId);
    }
    else
    {
        g_EventThread->m_Exited = TRUE;
    }
    VerbOut("Thread exited: %lx.%lx, code %X\n",
            g_EventProcessSysId, g_EventThreadSysId, ExitCode);

    g_LastEventType = DEBUG_EVENT_EXIT_THREAD;
    g_LastEventInfo.ExitThread.ExitCode = ExitCode;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.ExitThread);
    if (g_EventThread == NULL)
    {
        sprintf(g_LastEventDesc, "Exit thread ???:%x, code %X",
                g_EventThreadSysId, ExitCode);
    }
    else
    {
        sprintf(g_LastEventDesc, "Exit thread %d:%x, code %X",
                g_EventThread->m_UserId, g_EventThreadSysId, ExitCode);
    }
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_EXIT_THREAD];

    EventStatus =
        IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;

    // If we were stepping on this thread then force a breakin
    // so it's clear to the user that the thread exited.
    if (g_EventThread != NULL &&
        (g_StepTraceBp->m_Flags & DEBUG_BREAKPOINT_ENABLED) &&
        (g_StepTraceBp->m_MatchThread == g_EventThread ||
         g_DeferBp->m_MatchThread == g_EventThread))
    {
        WarnOut("WARNING: Step/trace thread exited\n");
        g_WatchFunctions.End(NULL);
        EventStatus = DEBUG_STATUS_BREAK;
        // Ensure that p/t isn't repeated.
        g_LastCommand[0] = 0;
    }
    
    EventStatus = ExecuteEventCommand(EventStatus,
                                      Filter->Command.Client,
                                      Filter->Command.Command[0]);
    
    ExitThreadEventApcData ApcData;
    ApcData.m_ExitCode = ExitCode;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyCreateProcessEvent(ULONG64 ImageFileHandle,
                         HANDLE SymHandle,
                         ULONG64 SysHandle,
                         ULONG64 BaseOffset,
                         ULONG ModuleSize,
                         PSTR ModuleName,
                         PSTR ImageName,
                         ULONG CheckSum,
                         ULONG TimeDateStamp,
                         ULONG64 InitialThreadHandle,
                         ULONG64 ThreadDataOffset,
                         ULONG64 StartOffset,
                         ULONG Flags,
                         ULONG Options,
                         ULONG InitialThreadFlags,
                         BOOL QueryImageInfo,
                         ULONG64 ImageNameOffset,
                         BOOL ImageNameUnicode)
{
    CHAR NameBuffer[MAX_IMAGE_PATH];
    char ModuleNameBuffer[MAX_MODULE];
    
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Create process %x\n", g_EventProcessSysId);

    ProcessInfo* Process;
    
    // If a process attach times out and the process is examined,
    // there's a possibility that the attach may succeed later,
    // yielding events for processes and threads already created
    // by examination.  In that case just check for an ID match
    // as the handles will be different.

    ForTargetProcesses(g_EventTarget)
    {
        if (((Process->m_Flags & ENG_PROC_EXAMINED) ||
             Process->m_SysHandle == SysHandle) &&
            Process->m_SystemId == g_EventProcessSysId)
        {
            // We already know about this process, just
            // ignore the event.
            if ((Process->m_Flags & ENG_PROC_EXAMINED) == 0)
            {
                WarnOut("WARNING: Duplicate process create event for %x\n",
                        g_EventProcessSysId);
            }
            return DEBUG_STATUS_IGNORE_EVENT;
        }
    }

    ThreadInfo* Thread;
    
    Process = new ProcessInfo(g_EventTarget, g_EventProcessSysId,
                              SymHandle, SysHandle, Flags, Options);
    if (Process)
    {
        Thread = new ThreadInfo(Process, g_EventThreadSysId,
                                ThreadDataOffset, InitialThreadHandle,
                                InitialThreadFlags, StartOffset);
    }
    else
    {
        Thread = NULL;
    }

    if (!Process || !Thread)
    {
        // Clean up the process in case one was created.
        delete Process;
        
        ErrOut("Unable to allocate process record for create process event\n");
        ErrOut("Process %x will be lost\n", g_EventProcessSysId);

        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_CREATE_PROCESS;
            sprintf(g_LastEventDesc, "Can't create process %x",
                    g_EventProcessSysId);
        }
        
        // Can't really continue the notification.
        return DEBUG_STATUS_BREAK;
    }
    
    // Look up infos now that they've been added.
    FindEventProcessThread();
    if (g_EventProcess == NULL || g_EventThread == NULL)
    {
        // This should never happen with the above failure
        // checks but handle it just in case.
        ErrOut("Create process unable to locate process or thread %x:%x\n",
               g_EventProcessSysId, g_EventThreadSysId);
        return DEBUG_STATUS_BREAK;
    }

    VerbOut("Process created: %lx.%lx\n",
            g_EventProcessSysId, g_EventThreadSysId);

    if (g_EngNotify > 0)
    {
        // This call is just to update internal process state.
        // Do not make real callbacks.
        g_EventTarget->m_ProcessesAdded = TRUE;
        return DEBUG_STATUS_NO_CHANGE;
    }
    
    g_EventTarget->OutputProcessesAndThreads("*** Create process ***");

    g_LastEventType = DEBUG_EVENT_CREATE_PROCESS;
    sprintf(g_LastEventDesc, "Create process %d:%x",
            g_EventProcess->m_UserId, g_EventProcessSysId);
    
    // Simulate a load module event for the process but do
    // not send it to the client.
    g_EngNotify++;
    
    if (QueryImageInfo)
    {
        GetEventName(ImageFileHandle, BaseOffset,
                     ImageNameOffset, (WORD)ImageNameUnicode,
                     NameBuffer, sizeof(NameBuffer));
        GetHeaderInfo(g_EventProcess, BaseOffset,
                      &CheckSum, &TimeDateStamp, &ModuleSize);
        CreateModuleNameFromPath(NameBuffer, ModuleNameBuffer);
        ImageName = NameBuffer;
        ModuleName = ModuleNameBuffer;
    }

    NotifyLoadModuleEvent(ImageFileHandle, BaseOffset, ModuleSize,
                          ModuleName, ImageName, CheckSum, TimeDateStamp,
                          IS_USER_TARGET(g_EventTarget));
    
    g_EngNotify--;

    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_CREATE_PROCESS];
    BOOL MatchesEvent;

    MatchesEvent = BreakOnThisImageTail(ImageName, Filter->Argument);

    EventStatus =
        (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
         MatchesEvent) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    g_EventTarget->m_ProcessesAdded = TRUE;
    
    CreateProcessEventApcData ApcData;
    ApcData.m_ImageFileHandle = ImageFileHandle;
    ApcData.m_Handle = SysHandle;
    ApcData.m_BaseOffset = BaseOffset;
    ApcData.m_ModuleSize = ModuleSize;
    ApcData.m_ModuleName = ModuleName;
    ApcData.m_ImageName = ImageName;
    ApcData.m_CheckSum = CheckSum;
    ApcData.m_TimeDateStamp = TimeDateStamp;
    ApcData.m_InitialThreadHandle = InitialThreadHandle;
    ApcData.m_ThreadDataOffset = ThreadDataOffset;
    ApcData.m_StartOffset = StartOffset;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyExitProcessEvent(ULONG ExitCode)
{
    StartOutLine(DEBUG_OUTPUT_VERBOSE, OUT_LINE_NO_PREFIX);
    VerbOut("*** Exit process\n");
    
    g_EngDefer |= ENG_DEFER_DELETE_EXITED;
    // There's a small possibility that exit events can
    // be delivered when the engine is not expecting them.
    // When attaching to a process that's exiting it's possible
    // to get an exit but no create.  When restarting it's
    // possible that not all events were successfully drained.
    // Protect this code from faulting in that case.
    if (g_EventProcess == NULL)
    {
        WarnOut("WARNING: Unknown process exit: %lx.%lx\n",
                g_EventProcessSysId, g_EventThreadSysId);
    }
    else
    {
        g_EventProcess->m_Exited = TRUE;
    }
    VerbOut("Process exited: %lx.%lx, code %X\n",
            g_EventProcessSysId, g_EventThreadSysId, ExitCode);

    g_LastEventType = DEBUG_EVENT_EXIT_PROCESS;
    g_LastEventInfo.ExitProcess.ExitCode = ExitCode;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.ExitProcess);
    if (g_EventProcess == NULL)
    {
        sprintf(g_LastEventDesc, "Exit process ???:%x, code %X",
                g_EventProcessSysId, ExitCode);
    }
    else
    {
        sprintf(g_LastEventDesc, "Exit process %d:%x, code %X",
                g_EventProcess->m_UserId, g_EventProcessSysId, ExitCode);
    }
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_EXIT_PROCESS];
    BOOL MatchesEvent;

    if (g_EventProcess && g_EventProcess->m_ExecutableImage)
    {
        MatchesEvent =
            BreakOnThisImageTail(g_EventProcess->m_ExecutableImage->
                                 m_ImagePath, Filter->Argument);
    }
    else
    {
        // If this process doesn't have a specific name always break.
        MatchesEvent = TRUE;
    }
    
    EventStatus =
        ((g_EngOptions & DEBUG_ENGOPT_FINAL_BREAK) ||
         (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
          MatchesEvent)) ?
        DEBUG_STATUS_BREAK : DEBUG_STATUS_IGNORE_EVENT;

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    ExitProcessEventApcData ApcData;
    ApcData.m_ExitCode = ExitCode;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifyLoadModuleEvent(ULONG64 ImageFileHandle,
                      ULONG64 BaseOffset,
                      ULONG ModuleSize,
                      PSTR ModuleName,
                      PSTR ImagePathName,
                      ULONG CheckSum,
                      ULONG TimeDateStamp,
                      BOOL UserMode)
{
    if (!g_EventProcess)
    {
        ErrOut("ERROR: Module load event for unknown process\n");
        
        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_LOAD_MODULE;
            sprintf(g_LastEventDesc, "Ignored load module at %s",
                    FormatAddr64(BaseOffset));
        }

        return DEBUG_STATUS_BREAK;
    }

    MODULE_INFO_ENTRY ModEntry = {0};
    ImageInfo* ImageEntry;

    ModEntry.NamePtr       = ImagePathName;
    ModEntry.File          = (HANDLE)ImageFileHandle;
    ModEntry.Base          = BaseOffset;
    ModEntry.Size          = ModuleSize;
    ModEntry.CheckSum      = CheckSum;
    ModEntry.ModuleName    = ModuleName;
    ModEntry.TimeDateStamp = TimeDateStamp;
    ModEntry.UserMode      = UserMode ? TRUE : FALSE;

    if (g_EventProcess->AddImage(&ModEntry, FALSE, &ImageEntry) != S_OK)
    {
        ImageEntry = NULL;
    }

    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_LOAD_MODULE];

    //
    // ntsd has always shown mod loads by default.
    //

    if (IS_USER_TARGET(g_EventTarget))
    {
        //if (Filter->Params.ExecutionOption == DEBUG_FILTER_OUTPUT)
        {
            StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
            dprintf("ModLoad: %s %s   %-8s\n",
                    FormatAddr64(BaseOffset),
                    FormatAddr64(BaseOffset + ModuleSize),
                    ImagePathName);
        }
    }

    g_EventTarget->OutputProcessesAndThreads("*** Load dll ***");

    if (g_EngNotify > 0)
    {
        g_EventProcess->m_ModulesLoaded = TRUE;
        return DEBUG_STATUS_IGNORE_EVENT;
    }
    
    g_LastEventType = DEBUG_EVENT_LOAD_MODULE;
    g_LastEventInfo.LoadModule.Base = BaseOffset;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.LoadModule);
    PrintString(g_LastEventDesc, DIMA(g_LastEventDesc),
                "Load module %.*s at %s",
                MAX_IMAGE_PATH - 32, ImagePathName, FormatAddr64(BaseOffset));
    
    ULONG EventStatus;
    BOOL MatchesEvent;

    if (!g_EventProcess->m_ModulesLoaded)
    {
        g_EngStatus |= ENG_STATUS_AT_INITIAL_MODULE_LOAD;
    }
    
    MatchesEvent = BreakOnThisImageTail(ImagePathName, Filter->Argument);
    
    if ((IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
         MatchesEvent) ||
        ((g_EngOptions & DEBUG_ENGOPT_INITIAL_MODULE_BREAK) &&
         !g_EventProcess->m_ModulesLoaded))
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    // If this is the very first module load give breakpoints
    // a chance to get established.  Execute the initial
    // module command if there is one.
    if (g_EngStatus & ENG_STATUS_AT_INITIAL_MODULE_LOAD)
    {
        // On NT4 boot the breakpoint update and context management caused
        // by this seems to hit the system at a fragile time and
        // usually causes a bugcheck 50, so don't do it.  Win2K seems
        // to be able to handle it, so allow it there.
        if (IS_USER_TARGET(g_EventTarget) ||
            g_EventTarget->m_ActualSystemVersion != NT_SVER_NT4)
        {
            SuspendExecution();
            RemoveBreakpoints();
            
            if (IS_EFEXECUTION_BREAK(g_EventFilters
                                     [DEBUG_FILTER_INITIAL_MODULE_LOAD].
                                     Params.ExecutionOption))
            {
                EventStatus = ExecuteEventCommand
                    (EventStatus,
                     g_EventFilters[DEBUG_FILTER_INITIAL_MODULE_LOAD].
                     Command.Client,
                     g_EventFilters[DEBUG_FILTER_INITIAL_MODULE_LOAD].
                     Command.Command[0]);
            }
        }
    }

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    g_EventProcess->m_ModulesLoaded = TRUE;

    LoadModuleEventApcData ApcData;
    ApcData.m_ImageFileHandle = ImageFileHandle;
    ApcData.m_BaseOffset = BaseOffset;
    ApcData.m_ModuleSize = ModuleSize;
    ApcData.m_ModuleName = ModuleName;
    ApcData.m_ImageName = ImagePathName;
    ApcData.m_CheckSum = CheckSum;
    ApcData.m_TimeDateStamp = TimeDateStamp;
    EventStatus = SendEvent(&ApcData, EventStatus);

    if (EventStatus > DEBUG_STATUS_GO_NOT_HANDLED &&
        EventStatus < DEBUG_STATUS_IGNORE_EVENT &&
        IS_KERNEL_TARGET(g_EventTarget) &&
        g_EventTarget->m_ActualSystemVersion == NT_SVER_NT4)
    {
        WarnOut("WARNING: Any modification to state may cause bugchecks.\n");
        WarnOut("         The debugger will not write "
                "any register changes.\n");
    }
    
    return EventStatus;
}

HRESULT
NotifyUnloadModuleEvent(PCSTR ImageBaseName,
                        ULONG64 BaseOffset)
{
    ImageInfo* Image = NULL;
    
    if (!g_EventProcess)
    {
        ErrOut("ERROR: Module unload event for unknown process\n");
        
        if (g_EngNotify == 0)
        {
            // Put in a placeholder description to make it easy
            // to identify this case.
            g_LastEventType = DEBUG_EVENT_UNLOAD_MODULE;
            sprintf(g_LastEventDesc, "Ignored unload module at %s",
                    FormatAddr64(BaseOffset));
        }

        return DEBUG_STATUS_BREAK;
    }
        
    // First try to look up the image by the base offset
    // as that's the most reliable identifier.
    if (BaseOffset)
    {
        Image = g_EventProcess->FindImageByOffset(BaseOffset, FALSE);
    }

    // Next try to look up the image by the full name given.
    if (!Image && ImageBaseName)
    {
        Image = g_EventProcess->FindImageByName(ImageBaseName, 0,
                                                INAME_IMAGE_PATH, FALSE);

        // Finally try to look up the image by the tail of the name given.
        if (!Image)
        {
            Image = g_EventProcess->FindImageByName(PathTail(ImageBaseName), 0,
                                                    INAME_IMAGE_PATH_TAIL,
                                                    FALSE);
        }
    }

    if (Image)
    {
        ImageBaseName = Image->m_ImagePath;
        BaseOffset = Image->m_BaseOfImage;
        Image->m_Unloaded = TRUE;
        g_EngDefer |= ENG_DEFER_DELETE_EXITED;
    }
    
    g_LastEventType = DEBUG_EVENT_UNLOAD_MODULE;
    g_LastEventInfo.UnloadModule.Base = BaseOffset;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.UnloadModule);
    PrintString(g_LastEventDesc, DIMA(g_LastEventDesc),
                "Unload module %.*s at %s",
                MAX_IMAGE_PATH - 32,
                ImageBaseName ? ImageBaseName : "<not found>",
                FormatAddr64(BaseOffset));
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_UNLOAD_MODULE];
    BOOL MatchesEvent;

    if (Filter->Params.ExecutionOption == DEBUG_FILTER_OUTPUT)
    {
        StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
        dprintf("%s\n", g_LastEventDesc);
    }

    MatchesEvent = BreakOnThisDllUnload(BaseOffset);
    
    if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) &&
        MatchesEvent)
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    if (MatchesEvent)
    {
        EventStatus = ExecuteEventCommand(EventStatus,
                                          Filter->Command.Client,
                                          Filter->Command.Command[0]);
    }
    
    UnloadModuleEventApcData ApcData;
    ApcData.m_ImageBaseName = ImageBaseName;
    ApcData.m_BaseOffset = BaseOffset;
    return SendEvent(&ApcData, EventStatus);
}

HRESULT
NotifySystemErrorEvent(ULONG Error,
                       ULONG Level)
{
    g_LastEventType = DEBUG_EVENT_SYSTEM_ERROR;
    g_LastEventInfo.SystemError.Error = Error;
    g_LastEventInfo.SystemError.Level = Level;
    g_LastEventExtraData = &g_LastEventInfo;
    g_LastEventExtraDataSize = sizeof(g_LastEventInfo.SystemError);
    sprintf(g_LastEventDesc, "System error %d.%d",
            Error, Level);
    
    if (Level <= g_SystemErrorOutput)
    {
        char ErrorString[_MAX_PATH];
        va_list Args;

        StartOutLine(DEBUG_OUTPUT_NORMAL, OUT_LINE_NO_PREFIX);
        dprintf("%s%s - %s: ",
                EventIdStr(),
                Level == SLE_WARNING ?
                "WARNING" : "ERROR", g_EventProcess->m_ImageHead->m_ImagePath);

        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL,
                      Error,
                      0,
                      ErrorString,
                      sizeof(ErrorString),
                      &Args);

        dprintf("%s", ErrorString);
    }
    
    ULONG EventStatus;
    EVENT_FILTER* Filter = &g_EventFilters[DEBUG_FILTER_SYSTEM_ERROR];

    if (IS_EFEXECUTION_BREAK(Filter->Params.ExecutionOption) ||
        Level <= g_SystemErrorBreak)
    {
        EventStatus = DEBUG_STATUS_BREAK;
    }
    else
    {
        EventStatus = DEBUG_STATUS_IGNORE_EVENT;
    }

    EventStatus = ExecuteEventCommand(EventStatus,
                                      Filter->Command.Client,
                                      Filter->Command.Command[0]);
    
    SystemErrorEventApcData ApcData;
    ApcData.m_Error = Error;
    ApcData.m_Level = Level;
    return SendEvent(&ApcData, EventStatus);
}
    
HRESULT
NotifySessionStatus(ULONG Status)
{
    SessionStatusApcData ApcData;
    ApcData.m_Status = Status;
    return SendEvent(&ApcData, DEBUG_STATUS_NO_CHANGE);
}

void
NotifyChangeDebuggeeState(ULONG Flags, ULONG64 Argument)
{
    if (g_EngNotify > 0)
    {
        // Notifications are being suppressed.
        return;
    }
    
    DebugClient* Client;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_EventInterest & DEBUG_EVENT_CHANGE_DEBUGGEE_STATE)
        {
            HRESULT Status;
            
            DBG_ASSERT(Client->m_EventCb != NULL);

            __try
            {
                Status = Client->m_EventCb->
                    ChangeDebuggeeState(Flags, Argument);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugEventCallbacks::"
                                              "ChangeDebuggeeState"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                Client->Destroy();
            }
        }
    }
}

void
NotifyChangeEngineState(ULONG Flags, ULONG64 Argument, BOOL HaveEngineLock)
{
    if (g_EngNotify > 0)
    {
        // Notifications are being suppressed.
        return;
    }

    DebugClient* Client;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_EventInterest & DEBUG_EVENT_CHANGE_ENGINE_STATE)
        {
            HRESULT Status;
            
            DBG_ASSERT(Client->m_EventCb != NULL);

            __try
            {
                Status = Client->m_EventCb->
                    ChangeEngineState(Flags, Argument);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugEventCallbacks::"
                                              "ChangeEngineState"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                Client->Destroy();
            }
        }
    }
}

void
NotifyChangeSymbolState(ULONG Flags, ULONG64 Argument, ProcessInfo* Process)
{
    if (g_EngNotify > 0)
    {
        // Notifications are being suppressed.
        return;
    }

    if ((Flags & (DEBUG_CSS_LOADS | DEBUG_CSS_UNLOADS)) &&
        Process)
    {
        // Reevaluate any offset expressions to account
        // for the change in symbols.
        EvaluateOffsetExpressions(Process, Flags);
    }
    
    DebugClient* Client;

    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_EventInterest & DEBUG_EVENT_CHANGE_SYMBOL_STATE)
        {
            HRESULT Status;
            
            DBG_ASSERT(Client->m_EventCb != NULL);

            __try
            {
                Status = Client->m_EventCb->
                    ChangeSymbolState(Flags, Argument);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugEventCallbacks::"
                                              "ChangeSymbolState"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                Client->Destroy();
            }
        }
    }
}

//----------------------------------------------------------------------------
//
// Input callbacks.
//
//----------------------------------------------------------------------------

//
// IMPORTANT: GetInput may be called in the middle of an operation.
// For example, an extension command may request input, so the engine
// is still in the middle of processing that command
// text.  Nothing in the engine should change at this point but
// it's necessary to suspend the engine lock to allow new
// connections and processing while waiting.  In theory
// all write operations should be prevented if g_InputNesting >= 1,
// indicating an input wait.  Right now key
// methods like WaitForEvent and Execute have such checks,
// but all writes should have them.  If you see problems
// with GetInput it might indicate a need for more checks.
//
ULONG g_InputNesting;

ULONG
GetInput(PCSTR Prompt,
         PSTR Buffer,
         ULONG BufferSize,
         ULONG Flags)
{
    DebugClient* Client;
    ULONG Len;
    HRESULT Status;

    // Start a new sequence for this input.
    g_InputSequence = 0;
    g_InputSizeRequested = BufferSize;
    g_InputNesting++;
    
    if (Prompt != NULL && Prompt[0] != 0)
    {
        dprintf("%s", Prompt);
    }

    SUSPEND_ENGINE();
    
    // Begin the input process by notifying all
    // clients with input callbacks that input
    // is needed.
    
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        // Update the input sequence for all clients so that
        // clients that don't have input callbacks can still
        // return input.  This is necessary in some threading cases.
        // This must occur before any callbacks are called as
        // the callbacks may use a different client for
        // ReturnInput.
        Client->m_InputSequence = 1;
    }
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_InputCb != NULL)
        {
            __try
            {
                Status = Client->m_InputCb->StartInput(BufferSize);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugInputCallbacks::"
                                              "StartInput"))
            {
                Status = E_FAIL;
            }

            if (Status != S_OK)
            {
                if (HRESULT_FACILITY(Status) == FACILITY_RPC)
                {
                    Client->Destroy();
                }
                else
                {
                    Len = 0;
                    ErrOut("Client %N refused StartInput, 0x%X\n",
                           Client, Status);
                    goto End;
                }
            }
        }
    }

    // Wait for input to be returned.
    if (WaitForSingleObject(g_InputEvent, INFINITE) != WAIT_OBJECT_0)
    {
        Len = 0;
        Status = WIN32_LAST_STATUS();
        ErrOut("Input event wait failed, 0x%X\n", Status);
    }
    else
    {
        ULONG CopyLen;
        
        Len = strlen(g_InputBuffer) + 1;
        CopyLen = min(Len, BufferSize);
        memcpy(Buffer, g_InputBuffer, CopyLen);
        Buffer[BufferSize - 1] = 0;
    }
    
 End:
    RESUME_ENGINE();
    
    g_InputSizeRequested = 0;
    g_InputNesting--;
    
    // Notify all clients that input process is done.
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_InputCb != NULL)
        {
            __try
            {
                Client->m_InputCb->EndInput();
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugInputCallbacks::"
                                              "EndInput"))
            {
            }
        }
    }
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        Client->m_InputSequence = 0xffffffff;
    }

    if (Len &&
        (Flags & GETIN_LOG_INPUT) &&
        g_LogFile >= 0)
    {
        ULONG BufLen = strlen(Buffer);
        if (BufLen)
        {
            _write(g_LogFile, Buffer, BufLen);
        }
        if (Flags & GETIN_LOG_INPUT_ADD_NEW_LINE)
        {
            _write(g_LogFile, "\n", 1);
        }
    }
        
    return Len;
}

//----------------------------------------------------------------------------
//
// Output callbacks.
//
//----------------------------------------------------------------------------

char g_OutBuffer[OUT_BUFFER_SIZE], g_FormatBuffer[OUT_BUFFER_SIZE];

char g_OutFilterPattern[MAX_IMAGE_PATH];
BOOL g_OutFilterResult = TRUE;

ULONG g_AllOutMask;

// Don't split up entries if they'll result in data so
// small that the extra callbacks are worse than the wasted space.
#define MIN_HISTORY_ENTRY_SIZE (256 + sizeof(OutHistoryEntryHeader))

PSTR g_OutHistory;
ULONG g_OutHistoryActualSize;
ULONG g_OutHistoryRequestedSize = 512 * 1024;
ULONG g_OutHistWriteMask;
OutHistoryEntry g_OutHistRead, g_OutHistWrite;
ULONG g_OutHistoryMask;
ULONG g_OutHistoryUsed;

ULONG g_OutputControl = DEBUG_OUTCTL_ALL_CLIENTS;
DebugClient* g_OutputClient;
BOOL g_BufferOutput;

#define BUFFERED_OUTPUT_SIZE 1024

// Largest delay allowed in TimedFlushCallbacks, in ticks.
#define MAX_FLUSH_DELAY 250

ULONG g_BufferedOutputMask;
char g_BufferedOutput[BUFFERED_OUTPUT_SIZE];
ULONG g_BufferedOutputUsed;
ULONG g_LastFlushTicks;

BOOL g_PartialOutputLine;
ULONG g_LastOutputMask;

void
CollectOutMasks(void)
{
    DebugClient* Client;

    g_AllOutMask = 0;
    for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
    {
        if (Client->m_OutputCb != NULL)
        {
            g_AllOutMask |= Client->m_OutMask;
        }
    }
}

BOOL
PushOutCtl(ULONG OutputControl, DebugClient* Client,
           OutCtlSave* Save)
{
    BOOL Status;

    FlushCallbacks();
    
    Save->OutputControl = g_OutputControl;
    Save->Client = g_OutputClient;
    Save->BufferOutput = g_BufferOutput;
    Save->OutputWidth = g_OutputWidth;
    Save->OutputLinePrefix = g_OutputLinePrefix;

    if (OutputControl == DEBUG_OUTCTL_AMBIENT)
    {
        // Leave settings unchanged.
        Status = TRUE;
    }
    else
    {
        ULONG SendMask = OutputControl & DEBUG_OUTCTL_SEND_MASK;
    
        if (
#if DEBUG_OUTCTL_THIS_CLIENT > 0
            SendMask < DEBUG_OUTCTL_THIS_CLIENT ||
#endif
            SendMask > DEBUG_OUTCTL_LOG_ONLY ||
            (OutputControl & ~(DEBUG_OUTCTL_SEND_MASK |
                               DEBUG_OUTCTL_NOT_LOGGED |
                               DEBUG_OUTCTL_OVERRIDE_MASK)))
        {
            Status = FALSE;
        }
        else
        {
            g_OutputControl = OutputControl;
            g_OutputClient = Client;
            g_BufferOutput = TRUE;
            if (Client != NULL)
            {
                g_OutputWidth = Client->m_OutputWidth;
                g_OutputLinePrefix = Client->m_OutputLinePrefix;
            }
            Status = TRUE;
        }
    }

    return Status;
}

void
PopOutCtl(OutCtlSave* Save)
{
    FlushCallbacks();
    g_OutputControl = Save->OutputControl;
    g_OutputClient = Save->Client;
    g_BufferOutput = Save->BufferOutput;
    g_OutputWidth = Save->OutputWidth;
    g_OutputLinePrefix = Save->OutputLinePrefix;
}

void
SendOutput(ULONG Mask, PCSTR Text)
{
    ULONG OutTo = g_OutputControl & DEBUG_OUTCTL_SEND_MASK;
    HRESULT Status;

    if (OutTo == DEBUG_OUTCTL_THIS_CLIENT)
    {
        if (g_OutputClient->m_OutputCb != NULL &&
            ((g_OutputControl & DEBUG_OUTCTL_OVERRIDE_MASK) ||
             (Mask & g_OutputClient->m_OutMask)))
        {
            __try
            {
                Status = g_OutputClient->m_OutputCb->Output(Mask, Text);
            }
            __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                              NULL, "IDebugOutputCallbacks::"
                                              "Output"))
            {
                Status = E_FAIL;
            }

            if (HRESULT_FACILITY(Status) == FACILITY_RPC)
            {
                g_OutputClient->Destroy();
            }
        }
    }
    else
    {
        DebugClient* Client;

        for (Client = g_Clients; Client != NULL; Client = Client->m_Next)
        {
            if ((OutTo == DEBUG_OUTCTL_ALL_CLIENTS ||
                 Client != g_OutputClient) &&
                Client->m_OutputCb != NULL &&
                ((g_OutputControl & DEBUG_OUTCTL_OVERRIDE_MASK) ||
                 (Client->m_OutMask & Mask)))
            {
                __try
                {
                    Status = Client->m_OutputCb->Output(Mask, Text);
                }
                __except(ExtensionExceptionFilter(GetExceptionInformation(),
                                                  NULL,
                                                  "IDebugOutputCallbacks::"
                                                  "Output"))
                {
                    Status = E_FAIL;
                }

                if (HRESULT_FACILITY(Status) == FACILITY_RPC)
                {
                    Client->Destroy();
                }
            }
        }
    }
}

void
BufferOutput(ULONG Mask, PCSTR Text, ULONG Len)
{
    EnterCriticalSection(&g_QuickLock);

    if (Mask != g_BufferedOutputMask ||
        g_BufferedOutputUsed + Len >= BUFFERED_OUTPUT_SIZE)
    {
        FlushCallbacks();

        if (Len >= BUFFERED_OUTPUT_SIZE)
        {
            SendOutput(Mask, Text);
            LeaveCriticalSection(&g_QuickLock);
            return;
        }

        g_BufferedOutputMask = Mask;
    }

    memcpy(g_BufferedOutput + g_BufferedOutputUsed, Text, Len + 1);
    g_BufferedOutputUsed += Len;
    
    LeaveCriticalSection(&g_QuickLock);
}

void
FlushCallbacks(void)
{
    EnterCriticalSection(&g_QuickLock);
    
    if (g_BufferedOutputUsed > 0)
    {
        SendOutput(g_BufferedOutputMask, g_BufferedOutput);
        g_BufferedOutputMask = 0;
        g_BufferedOutputUsed = 0;
        g_LastFlushTicks = GetTickCount();
    }

    LeaveCriticalSection(&g_QuickLock);
}

void
TimedFlushCallbacks(void)
{
    EnterCriticalSection(&g_QuickLock);

    if (g_BufferedOutputUsed > 0)
    {
        ULONG Ticks = GetTickCount();
    
        // Flush if the last flush was a "long" time ago.
        if (g_LastFlushTicks == 0 ||
            g_LastFlushTicks > Ticks ||
            (Ticks - g_LastFlushTicks) > MAX_FLUSH_DELAY)
        {
            FlushCallbacks();
        }
    }

    LeaveCriticalSection(&g_QuickLock);
}

#if 0
#define DBGHIST(Args) g_NtDllCalls.DbgPrint Args
#else
#define DBGHIST(Args)
#endif

void
WriteHistoryEntry(ULONG Mask, PCSTR Text, ULONG Len)
{
    PSTR Buf;

    DBG_ASSERT((PSTR)g_OutHistWrite + sizeof(OutHistoryEntryHeader) +
               Len + 1 <= g_OutHistory + g_OutHistoryActualSize);
    
    if (Mask != g_OutHistWriteMask)
    {
        // Start new entry.
        g_OutHistWrite->Mask = Mask;
        g_OutHistWriteMask = Mask;
        Buf = (PSTR)(g_OutHistWrite + 1);
        g_OutHistoryUsed += sizeof(OutHistoryEntryHeader);
        
        DBGHIST(("  Write new "));
    }
    else
    {
        // Merge with previous entry.
        Buf = (PSTR)g_OutHistWrite - 1;
        g_OutHistoryUsed--;

        DBGHIST(("  Merge old "));
    }

    DBGHIST(("entry %p:%X, %d\n", g_OutHistWrite, Mask, Len));
    
    // Len does not include the terminator here so
    // always append a terminator.
    memcpy(Buf, Text, Len);
    Buf += Len;
    *Buf++ = 0;

    g_OutHistWrite = (OutHistoryEntry)Buf;
    g_OutHistoryUsed += Len + 1;
    DBG_ASSERT(g_OutHistoryUsed <= g_OutHistoryActualSize);
}

void
AddToOutputHistory(ULONG Mask, PCSTR Text, ULONG Len)
{
    if (Len == 0 || g_OutHistoryRequestedSize == 0)
    {
        return;
    }

    if (g_OutHistory == NULL)
    {
        // Output history buffer hasn't been allocated yet,
        // so go ahead and do it now.
        g_OutHistory = (PSTR)malloc(g_OutHistoryRequestedSize);
        if (g_OutHistory == NULL)
        {
            return;
        }
        
        // Reserve space for a trailing header as terminator.
        g_OutHistoryActualSize = g_OutHistoryRequestedSize -
            sizeof(OutHistoryEntryHeader);
    }
 
    ULONG TotalLen = Len + sizeof(OutHistoryEntryHeader) + 1;

    DBGHIST(("Add %X, %d\n", Mask, Len));
    
    if (TotalLen > g_OutHistoryActualSize)
    {
        Text += TotalLen - g_OutHistoryActualSize;
        TotalLen = g_OutHistoryActualSize;
        Len = TotalLen - sizeof(OutHistoryEntryHeader) - 1;
    }
    
    if (g_OutHistWrite == NULL)
    {
        g_OutHistRead = (OutHistoryEntry)g_OutHistory;
        g_OutHistWrite = (OutHistoryEntry)g_OutHistory;
        g_OutHistWriteMask = 0;
    }

    while (Len > 0)
    {
        ULONG Left;

        if (g_OutHistoryUsed == 0 || g_OutHistWrite > g_OutHistRead)
        {
            Left = g_OutHistoryActualSize -
                (ULONG)((PSTR)g_OutHistWrite - g_OutHistory);

            if (TotalLen > Left)
            {
                // See if it's worth splitting this request to
                // fill the space at the end of the buffer.
                if (Left >= MIN_HISTORY_ENTRY_SIZE &&
                    (TotalLen - Left) >= MIN_HISTORY_ENTRY_SIZE)
                {
                    ULONG Used = Left - sizeof(OutHistoryEntryHeader) - 1;
                
                    // Pack as much data as possible into the
                    // end of the buffer.
                    WriteHistoryEntry(Mask, Text, Used);
                    Text += Used;
                    Len -= Used;
                    TotalLen -= Used;
                }

                // Terminate the buffer and wrap around.  A header's
                // worth of space is reserved at the buffer end so
                // there should always be enough space for this.
                DBG_ASSERT((ULONG)((PSTR)g_OutHistWrite - g_OutHistory) <=
                           g_OutHistoryActualSize);
                g_OutHistWrite->Mask = 0;
                g_OutHistWriteMask = 0;
                g_OutHistWrite = (OutHistoryEntry)g_OutHistory;
                Left = (ULONG)((PUCHAR)g_OutHistRead - (PUCHAR)g_OutHistWrite);
            }
        }
        else
        {
            Left = (ULONG)((PUCHAR)g_OutHistRead - (PUCHAR)g_OutHistWrite);
        }

        if (TotalLen > Left)
        {
            ULONG Need = TotalLen - Left;
        
            // Advance the read pointer to make room.
            while (Need > 0)
            {
                PSTR EntText = (PSTR)(g_OutHistRead + 1);
                ULONG EntTextLen = strlen(EntText);
                ULONG EntTotal =
                    sizeof(OutHistoryEntryHeader) + EntTextLen + 1;

                if (EntTotal <= Need ||
                    EntTotal - Need < MIN_HISTORY_ENTRY_SIZE)
                {
                    DBGHIST(("  Remove %p:%X, %d\n", g_OutHistRead,
                             g_OutHistRead->Mask, EntTextLen));
                    
                    // Remove the whole entry.
                    g_OutHistRead = (OutHistoryEntry)
                        ((PUCHAR)g_OutHistRead + EntTotal);
                    DBG_ASSERT((ULONG)((PSTR)g_OutHistRead - g_OutHistory) <=
                               g_OutHistoryActualSize);
                    if (g_OutHistRead->Mask == 0)
                    {
                        g_OutHistRead = (OutHistoryEntry)g_OutHistory;
                    }
                    
                    Need -= EntTotal <= Need ? EntTotal : Need;
                    DBG_ASSERT(g_OutHistoryUsed >= EntTotal);
                    g_OutHistoryUsed -= EntTotal;
                }
                else
                {
                    OutHistoryEntryHeader EntHdr = *g_OutHistRead;

                    DBGHIST(("  Trim %p:%X, %d\n", g_OutHistRead,
                             g_OutHistRead->Mask, EntTextLen));
                    
                    // Remove part of the head of the entry.
                    g_OutHistRead = (OutHistoryEntry)
                        ((PUCHAR)g_OutHistRead + Need);
                    DBG_ASSERT((ULONG)
                               ((PSTR)g_OutHistRead + (EntTotal - Need) -
                                g_OutHistory) <= g_OutHistoryActualSize);
                    *g_OutHistRead = EntHdr;
                    DBG_ASSERT(g_OutHistoryUsed >= Need);
                    g_OutHistoryUsed -= Need;
                    Need = 0;
                }

                DBGHIST(("  Advance read to %p:%X\n",
                         g_OutHistRead, g_OutHistRead->Mask));
            }
        }
        else
        {
            WriteHistoryEntry(Mask, Text, Len);
            break;
        }
    }
    
    DBGHIST(("History read %p, write %p, used %d\n",
             g_OutHistRead, g_OutHistWrite, g_OutHistoryUsed));
}

void
SendOutputHistory(DebugClient* Client, ULONG HistoryLimit)
{
    if (g_OutHistRead == NULL ||
        Client->m_OutputCb == NULL ||
        (Client->m_OutMask & g_OutHistoryMask) == 0 ||
        HistoryLimit == 0)
    {
        return;
    }

    FlushCallbacks();
    
    OutHistoryEntry Ent;
    ULONG Total;
    ULONG Len;

    Ent = g_OutHistRead;
    Total = 0;
    while (Ent != g_OutHistWrite)
    {
        if (Ent->Mask == 0)
        {
            Ent = (OutHistoryEntry)g_OutHistory;
        }

        PSTR Text = (PSTR)(Ent + 1);
        Len = strlen(Text);
        Total += Len;

        Ent = (OutHistoryEntry)(Text + Len + 1);
    }

    DBGHIST(("Total history %X\n", Total));
    
    Ent = g_OutHistRead;
    while (Ent != g_OutHistWrite)
    {
        if (Ent->Mask == 0)
        {
            Ent = (OutHistoryEntry)g_OutHistory;
        }

        PSTR Text = (PSTR)(Ent + 1);
        Len = strlen(Text);

        if (Total - Len <= HistoryLimit)
        {
            PSTR Part = Text;
            if (Total > HistoryLimit)
            {
                Part += Total - HistoryLimit;
            }
            
            DBGHIST(("Send %p:%X, %d\n",
                     Ent, Ent->Mask, strlen(Part)));

            Client->m_OutputCb->Output(Ent->Mask, Part);
        }

        Total -= Len;
        Ent = (OutHistoryEntry)(Text + Len + 1);
    }
}

void
CompletePartialLine(ULONG MatchMask)
{
    // There are often cases where special output, such
    // as !sym noisy output, will come in the middle
    // of some other output.  If the last output was
    // a partial line and not special output, insert
    // a newline so that the special output isn't stuck
    // out on the end of a line somewhere.
    if (g_PartialOutputLine && g_LastOutputMask != MatchMask)
    {
        dprintf("\n");
    }
}

void
StartOutLine(ULONG Mask, ULONG Flags)
{
    if ((Flags & OUT_LINE_NO_TIMESTAMP) == 0 &&
        g_EchoEventTimestamps)
    {
        MaskOut(Mask, "%s: ", TimeToStr((ULONG)time(NULL)));
    }
    
    if ((Flags & OUT_LINE_NO_PREFIX) == 0 &&
        g_OutputLinePrefix)
    {
        MaskOut(Mask, "%s", g_OutputLinePrefix);
    }
}

//
// Translates various printf formats to account for the target platform.
//
// This looks for %p type format and truncates the top 4 bytes of the ULONG64
// address argument if the debugee is a 32 bit machine.
// The %p is replaced by %I64x in format string.
//
BOOL
TranslateFormat(
    LPSTR formatOut,
    LPCSTR format,
    va_list args,
    ULONG formatOutSize,
    BOOL Ptr64
    )
{
#define Duplicate(j,i) (formatOut[j++] = format[i++])
    ULONG minSize = strlen(format), i = 0, j = 0;
    CHAR c;
    BOOL TypeFormat = FALSE;
    BOOL FormatChanged = FALSE;

    do
    {
        c = format[i];

        if (c=='%')
        {
            TypeFormat = !TypeFormat;
        }
        if (TypeFormat)
        {
            switch (c)
            {
            case 'c': case 'C': case 'i': case 'd':
            case 'o': case 'u': case 'x': case 'X':
                Duplicate(j,i);
                (void)va_arg(args, int);
                TypeFormat = FALSE;
                break;
            case 'e': case 'E': case 'f': case 'g':
            case 'G':
                Duplicate(j,i);
                (void)va_arg(args, double);
                TypeFormat = FALSE;
                break;
            case 'n':
                Duplicate(j,i);
                (void)va_arg(args, int*);
                TypeFormat = FALSE;
                break;
            case 'N':
                // Native pointer, turns into %p.
                formatOut[j++] = 'p';
                FormatChanged = TRUE;
                i++;
                (void)va_arg(args, void*);
                TypeFormat = FALSE;
                break;
            case 's': case 'S':
                Duplicate(j,i);
                (void)va_arg(args, char*);
                TypeFormat = FALSE;
                break;

            case 'I':
                if ((format[i+1] == '6') && (format[i+2] == '4'))
                {
                    Duplicate(j,i);
                    Duplicate(j,i);
                    (void)va_arg(args, ULONG64);
                    TypeFormat = FALSE;
                }
                // dprintf("I64 a0 %lx, off %lx\n", args.a0, args.offset);
                Duplicate(j,i);
                break;
            
            case 'z': case 'Z':
                // unicode string
                Duplicate(j,i);
                (void)va_arg(args, void*);
                TypeFormat = FALSE;
                break;

            case 'p':
            case 'P':
                minSize +=3;
                if (format[i-1] == '%')
                {
                    minSize++;
                    if (Ptr64)
                    {
                        minSize += 2;
                        if (minSize > formatOutSize)
                        {
                            return FALSE;
                        }
                        formatOut[j++] = '0';
                        formatOut[j++] = '1';
                        formatOut[j++] = '6';
                    }
                    else
                    {
                        if (minSize > formatOutSize)
                        {
                            return FALSE;
                        }
                        formatOut[j++] = '0';
                        formatOut[j++] = '8';
                    }
                }

                if (minSize > formatOutSize)
                {
                    return FALSE;
                }
                formatOut[j++] = 'I';
                formatOut[j++] = '6';
                formatOut[j++] = '4';
                formatOut[j++] = (c == 'p') ? 'x' : 'X'; ++i;
                FormatChanged = TRUE;

                if (!Ptr64)
                {
                    PULONG64 Arg;

                    Arg = (PULONG64) (args);

                    //
                    // Truncate signextended addresses
                    //
                    *Arg = (ULONG64) (ULONG) *Arg;
                }

                (void)va_arg(args, ULONG64);
                TypeFormat = FALSE;
                break;

            default:
                Duplicate(j,i);
            } /* switch */
        }
        else
        {
            Duplicate(j,i);
        }
    }
    while (format[i] != '\0');

    formatOut[j] = '\0';
    return FormatChanged;
#undef Duplicate
}

void
MaskOutVa(ULONG Mask, PCSTR Format, va_list Args, BOOL Translate)
{
    int Len;
    ULONG OutTo = g_OutputControl & DEBUG_OUTCTL_SEND_MASK;

    // Reject output as quickly as possible to avoid
    // doing the format translation and sprintf.
    if (OutTo == DEBUG_OUTCTL_IGNORE ||
        (((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) ||
          (Mask & g_OutHistoryMask) == 0) &&
         ((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) ||
          (Mask & g_LogMask) == 0 ||
          g_LogFile == -1) &&
         (OutTo == DEBUG_OUTCTL_LOG_ONLY ||
          ((g_OutputControl & DEBUG_OUTCTL_OVERRIDE_MASK) == 0 &&
           (OutTo == DEBUG_OUTCTL_THIS_CLIENT &&
            ((Mask & g_OutputClient->m_OutMask) == 0 ||
             g_OutputClient->m_OutputCb == NULL)) ||
           (Mask & g_AllOutMask) == 0))))
    {
        return;
    }

    // Do not suspend the engine lock as this may be called
    // in the middle of an operation.

    EnterCriticalSection(&g_QuickLock);
    
    __try
    {
        if (Translate &&
            TranslateFormat(g_FormatBuffer, Format, Args, OUT_BUFFER_SIZE - 1,
                            g_Machine ? g_Machine->m_Ptr64 : TRUE))
        {
            Len = _vsnprintf(g_OutBuffer, OUT_BUFFER_SIZE - 1,
                             g_FormatBuffer, Args);
        }
        else
        {
            Len = _vsnprintf(g_OutBuffer, OUT_BUFFER_SIZE - 1, Format, Args);
        }
        if (Len == 0)
        {
            __leave;
        }
        else if (Len < 0)
        {
            Len = OUT_BUFFER_SIZE - 1;
            g_OutBuffer[Len] = 0;
        }

        // Check and see if this output is filtered away.
        if ((Mask & DEBUG_OUTPUT_DEBUGGEE) &&
            g_OutFilterPattern[0] &&
            !(MatchPattern(g_OutBuffer, g_OutFilterPattern) ==
              g_OutFilterResult))
        {
            __leave;
        }
        
        // If the caller doesn't think this output should
        // be logged it probably also shouldn't go in the
        // history.
        if ((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) == 0 &&
            (Mask & g_OutHistoryMask))
        {
            AddToOutputHistory(Mask, g_OutBuffer, Len);
        }
        
        if ((g_OutputControl & DEBUG_OUTCTL_NOT_LOGGED) == 0 &&
            (Mask & g_LogMask) &&
            g_LogFile != -1)
        {
            _write(g_LogFile, g_OutBuffer, Len);
        }

        if (OutTo == DEBUG_OUTCTL_LOG_ONLY)
        {
            __leave;
        }

        if (g_OutBuffer[Len - 1] != '\n' &&
            g_OutBuffer[Len - 1] != '\r')
        {
            // The current output is not a complete line.
            g_PartialOutputLine = TRUE;
        }
        else
        {
            g_PartialOutputLine = FALSE;
        }
        g_LastOutputMask = Mask;
        
        if (g_BufferOutput)
        {
            BufferOutput(Mask, g_OutBuffer, Len);
        }
        else
        {
            SendOutput(Mask, g_OutBuffer);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        OutputDebugStringA("Exception in MaskOutVa\n");
    }

    LeaveCriticalSection(&g_QuickLock);
}

void __cdecl
MaskOut(ULONG Mask, PCSTR Format, ...)
{
    va_list Args;
    va_start(Args, Format);
    MaskOutVa(Mask, Format, Args, TRUE);
    va_end(Args);
}

void __cdecl
dprintf(PCSTR Format, ...)
{
    va_list Args;
    va_start(Args, Format);
    MaskOutVa(DEBUG_OUTPUT_NORMAL, Format, Args, FALSE);
    va_end(Args);
}

#define OUT_FN(Name, Mask)                      \
void __cdecl                                    \
Name(PCSTR Format, ...)                         \
{                                               \
    va_list Args;                               \
    va_start(Args, Format);                     \
    MaskOutVa(Mask, Format, Args, TRUE);        \
    va_end(Args);                               \
}

OUT_FN(dprintf64, DEBUG_OUTPUT_NORMAL)
OUT_FN(ErrOut,    DEBUG_OUTPUT_ERROR)
OUT_FN(WarnOut,   DEBUG_OUTPUT_WARNING)
OUT_FN(VerbOut,   DEBUG_OUTPUT_VERBOSE)
OUT_FN(BpOut,     DEBUG_IOUTPUT_BREAKPOINT)
OUT_FN(EventOut,  DEBUG_IOUTPUT_EVENT)
OUT_FN(KdOut,     DEBUG_IOUTPUT_KD_PROTOCOL)
